/**
 * @file main.c
 * @author lcz (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-11-02
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "applib/lib_syscall.h"
#include "comm/types.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "my_shell/main.h"
#include <getopt.h>
#include <sys/file.h>
#include "fs/file.h"
#include "dev/tty.h"
/**`
 * @brief 很奇怪 我的\n 放在最后无法背newlib库识别到 size大小错误
 * 我的建议是前后都包括一下\n
 * 转义字符的文字这块很奇怪，最好不要用，我都不知道哪里错了，没有源码级调试 newlib
 */

static cli_t cli;
static const char *promot = "sh >> ";
char cmd_buf[256];
/**
 * @brief 查找内置指令
 * @param name 指令名称
 * @return * const cli_cmd_t* 放回指令结构体
 */
static const cli_cmd_t *find_builtin(const char *name)
{
    // 遍历指令集合
    for (const cli_cmd_t *cmd = cli.cmd_start; cmd < cli.cmd_end; cmd++)
    {

        if (strcmp(cmd->name, name) != 0)
        {
            continue;
        }
        return cmd;
    }
    // 没有找到
    return (const cli_cmd_t *)0;
}
/**
 * @brief 运行内部指令
 * @param cmd  指令指针
 */
static void run_builtin(const cli_cmd_t *cmd, int argc, char **argv)
{
    int ret = cmd->do_func(argc, argv);
    // 发生错误，检测错误码
    if (ret < 0)
    {
        fprintf(stderr, "error:%d\n", ret);
    }
}
/**
 * @brief 寻找指令
 * @param filename 文件名
 * @return * const char* 
 */
static const char *find_exec_path(const char *filename)
{
    static char path[255];
    // 可以打开打开这个文件就判断路径合法
    int fd = open(filename, 0);
    if (fd < 0)
    {
        sprintf(path, "%s.elf", filename);
        fd = open(path, 0);
        if (fd < 0)
        {
            return (const char *)0;
        }
        close(fd);
        return path;
    }
    else
    {
        close(fd);
        return filename;
    }
}

/**
 * @brief shell内置do_help命令
 */
static int do_help(int argc, char **argv)
{
    const cli_cmd_t *start = cli.cmd_start;
    while (start < cli.cmd_end)
    {
        printf("%s %s\n", start->name, start->usage);
        start++;
    }
    return 0;
}
/**
 * @brief shell内置指令，清屏操作
 */
static int do_clear(int argc, char **argv)
{
    printf("%s", ESC_CLEAR_SCREEN);
    printf("%s", ESC_MOVE_CURSOR(0, 0));
    return 0;
}
/**
 * @brief shell内置echo指令
 */
static int do_echo(int argc, char **argv)
{
    // 参数为1个
    // 只输入了echo 命令，连要打印的字符都没有添加
    if (argc == 1)
    {
        // 等待输入 字符串 没有参数可选了 -n -h 无法使用
        char msg_buf[128];
        fgets(msg_buf, sizeof(msg_buf), stdin);
        msg_buf[sizeof(msg_buf) - 1] = '\0';
        puts(msg_buf);
        return 0;
    }
    // -n 打印次数，默认为1
    int count = 1;
    int ch;
    // newbib库有 标准库无 n: 表示 n后面必有参数
    // -h 使用方法
    while ((ch = getopt(argc, argv, "n:h")) != -1)
    {
        switch (ch)
        {
            // 打印help信息
        case 'h':
            puts("echo and message");
            puts("Usage: echo [-n count] message");
            optind = 1;
            return 0;
            // -n 参数处理
        case 'n':
            // atoi 标准C库函数
            count = atoi(optarg);
            break;
        case '?':
            // 错误处理
            if (optarg)
            {
                printf("** Unknown option:-%s **\n", optarg);
            }
            optind = 1;
            return -1;
        default:
            break;
        }
    }
    // 如果参数的索引大于 参数的个数-1 这存在问题，我们报错
    // optind 其实是newlib库的全局变量
    // getopt查找选项时，会从optidx指向的位置开始找，所以需要重新设置，另外，由于选项往往位于argv[1]及其以后，所以将该值设置成1
    if (optind > argc - 1)
    {
        fprintf(stderr, "Message is empty \n");
        optind = 1;
        return -1;
    }

    char *msg = argv[optind];
    for (int i = 0; i < count; i++)
    {
        puts(msg);
    }
    optind = 1;
    return 0;
    // 也可以再最前面设置好吧
}

static int do_less(int argc, char **argv)
{
    int line_mode = 0;
    int ch;
    // newbib库有 标准库无 n: 表示 n后面必有参数
    // -h 使用方法
    while ((ch = getopt(argc, argv, "lh")) != -1)
    {
        switch (ch)
        {
            // 打印help信息
        case 'h':
            puts("show file content");
            puts("less [-l] file");
            puts("-l show file line by line.");
            optind = 1;
            break;
        case 'l':
            line_mode = 1;
            break;
        case '?':
            // 错误处理
            if (optarg)
            {
                printf("** Unknown option:-%s **\n", optarg);
            }
            optind = 1;
            return -1;
        default:
            break;
        }
    }
    // 如果参数的索引大于 参数的个数-1 这存在问题，我们报错
    // optind 其实是newlib库的全局变量
    // getopt查找选项时，会从optidx指向的位置开始找，所以需要重新设置，另外，由于选项往往位于argv[1]及其以后，所以将该值设置成1
    if (optind > argc - 1)
    {
        printf("no file\n");
        optind = 1; // getopt需要多次调用，需要重置,其他地方的原因相同
        return -1;
    }
    FILE *file = fopen(argv[optind], "r");
    if (file == NULL)
    {
        printf("open file failed. %s", argv[optind]);
        optind = 1; // getopt需要多次调用，需要重置
        return -1;
    }

    char *buf = (char *)malloc(255);
    if (line_mode == 0)
    {
        while (fgets(buf, 255, file) != NULL)
        {
            puts(buf);
        }
    }
    else
    {
        // 设置无缓存
        setvbuf(stdin, NULL, _IONBF, 0);
        ioctl(0, TTY_CMD_ECHO, 0, 0);
        // 读取文件
        while (1)
        {
            char *b = fgets(buf, 255, file);
            if (b == NULL)
            {
                break;
            }
            puts(buf);
            int ch;
            while ((ch = fgetc(stdin)) != 'n')
            {
                if (ch == 'q')
                {
                    goto less_quit;
                }
            }
        }
    less_quit:
        setvbuf(stdin, NULL, _IOLBF, BUFSIZ);
        ioctl(0, TTY_CMD_ECHO, 1, 0);
    }

    free(buf);
    fclose(file);
    optind = 1; // getopt需要多次调用，需要重置

    return 0;
    // 也可以再最前面设置好吧
}

/**
 * @brief shell内置退出指令
 */
static int do_exit(int argc, char **argv)
{
    exit(0);
    // 执行exit系统调用的话，最终会提前退出进程函数，return 并不会返回
    return 0;
}
/**
 * @brief 打印目录中的文件
 */
static int do_ls(int argc, char **argv)
{
    // 我们仅支持对顶层目录的遍历
    DIR *p_dir = opendir("temp");
    if (p_dir == NULL)
    {
        printf("open dir failed.");
        return -1;
    }
    // 遍历目录中的目录项
    struct _dirent *entry;
    while ((entry = readdir(p_dir)) != NULL)
    {
        strlwr(entry->name);
        char type_char = 'n';
        switch (entry->type)
        {
        case FILE_UNKNOWN:
            type_char = 'n';
            break;
        case FILE_DIR:
            type_char = 'd';
            break;
        case FILE_NORMAL:
            type_char = 'f';
            break;
        }
        // 有点奇怪 不能直接加\t 需要添加空格
        printf("%c \t %s \t \t \t %d \n",
               type_char,
               entry->name,
               entry->size);
    }
    closedir(p_dir);
    return 0;
}

/**
 * @brief 文件的复制，源文件打开，写目的的文件
 * @return * int 
 */
static int do_cp(int argc,char ** argv)
{
    // 参数不够
    if (argc < 3)
    {
        printf("no [from] or [to]\n");
        return -1;
    }
    // FILE 结构体为newlib库提供的
    FILE *from, *to;
    from = fopen(argv[1], "rb");
    to = fopen(argv[2], "wb");
    // 目的文件，文件不存在，在c库里面传打开模式
    if (!from || !to)
    {
        printf("open file failed");
        goto cp_failed;
    }

    // 分配缓存
    char *buf = (char *)malloc(255);
    int size;
    while ((size = fread(buf, 1, 255, from)) > 0)
    {
        fwrite(buf, 1, size, to);
    }
    free(buf);
cp_failed:
    if (from)
    {
        fclose(from);
    }
    if (to)
    {
        fclose(to);
    }

    return 0;
}

static int do_rm(int argc,char ** argv){
    // 参数过少
    if (argc < 2)
    {
        printf("no specify fild");
        return -1;
    }
    // 系统调用
    int err = unlink(argv[1]);
    if (err < 0)
    {
        printf("rm file failed: %s", argv[1]);
        return err;
    }

    return 0;
}

    /**
     * @brief 处理加载磁盘文件
     * @param path 文件路径
     */
    static void
    run_exec_file(const char *path, int argc, char **argv)
{
    int pid = fork();
    if (pid < 0)
    {
        printf("fork failed %s", path);
    }
    else if (pid == 0)
    {
        // 子进程复制fork被创建，拷贝父进程代码，设备一样？
        // 环境变量
        // printf("fork succeeded!");
        int err = execve(path, argv, (char *const *)0);
        if (err < 0)
        {
            printf("exec failed: %s", path);
        }
        exit(-1);
    }
    else
    {
        // 父进程等待子进程执行
        int status;
        // wait 放回子进程pid
        int pid = wait(&status);
        printf("cmd %s result: %d,pid=%d\n", path, status, pid);
    }
}

static void cli_init(const char *promot, const cli_cmd_t *cmd_list, int size)
{
    cli.promot = promot;
    // 使用string库
    memset(cli.curr_input, 0, CLI_INPUT_SIZE);
    cli.cmd_start = cmd_list;
    cli.cmd_end = cmd_list + size;
}

static void show_promot(void)
{
    printf("%s", cli.promot);
    // 不能使用puts，puts默认加回车
    fflush(stdout);
}

// 命令表
static const cli_cmd_t cmd_list[] = {
    {
        .name = "help",
        .usage = "help -- list supported command",
        .do_func = do_help,
    },
    {
        .name = "clear",
        .usage = "clear -- clear screen",
        .do_func = do_clear,
    },
    {
        .name = "echo",
        .usage = "echo [-n  count] msg -- echo something",
        .do_func = do_echo,
    },
    {
        .name = "quit",
        .usage = "quit from shell",
        .do_func = do_exit,
    },
    {
        .name = "ls",
        .usage = "ls -- list director",
        .do_func = do_ls,
    },
    {
        .name = "less",
        .usage = "less [-l] file -- show file",
        .do_func = do_less,
    },
    {
        .name = "cp",
        .usage = "cp src dest -- copy file",
        .do_func = do_cp,
    },
    {
        .name="rm",
        .usage= "rm file -- remove file",
        .do_func=do_rm,
    }

};

int main(int argc, char **argv)
{
    // 通过文件系统操作tty设备
    // 让文件系统调用设备函数（dev_open() 等 ）;
    // open("tty:0", 0);
    open(argv[1], O_RDWR);
    dup(0); // 系统调用  赋值第0项,拷贝第0个进程的第0个文件描述符
    dup(0); // 系统调用  赋值第0项,拷贝第0个进程的第0个文件描述符

    // cmd_list 内置的数组形式的命令表 二位数组
    cli_init(promot, cmd_list, sizeof(cmd_list) / sizeof(cmd_list[0]));
    // 进入睡眠 其他终端进行初始化 避免日志打印乱掉
    msleep(1000);
    for (;;)
    {
        // newlib库函数，不用实现，他们最终调用sys_read 和 sys_write 函数
        show_promot();
        // gets(cli.curr_input);
        // puts(cli.curr_input);
        char *str = fgets(cli.curr_input, CLI_INPUT_SIZE, stdin);
        if (!str)
        {
            continue;
        }
        // 将\n 与 \r 进行替换 \0
        char *cr = strchr(cli.curr_input, '\n');
        if (cr)
        {
            *cr = '\0';
        }
        cr = strchr(cli.curr_input, '\r');
        if (cr)
        {
            *cr = '\0';
        }
        // 测试参数个数 echo -n  100 message for循环遍历
        // 此处调用newlib库函数
        // 字符串非空
        // 定义一个指针数组

        int argc = 0;
        char *argv[CLI_MAX_ARG_COUNT];
        memset(argv, 0, sizeof(argv));
        const char *space = " ";
        char *token = strtok(cli.curr_input, space);
        while (token)
        {
            argv[argc++] = token;
            // 指定从最开始的串开始扫描，不用再填写字串了
            // 找到第一个空格字符串并返回之前的字符
            token = strtok(NULL, space);
        }
        // 没有输入参数
        if (argc == 0)
        {
            continue;
        }
        // 输入参数过多
        if (argc >= 10)
        {
            puts("too many paramters!");
            continue;
        }
        // 查找argv的第零项   find_builtin 查找内部命令
        const cli_cmd_t *cmd = find_builtin(argv[0]);
        if (cmd)
        {
            run_builtin(cmd, argc, argv);
            continue;
        }
        // 执行加载磁盘文件并执行
        
        const char * path = find_exec_path(argv[0]);
        if(path){
            run_exec_file(path, argc, argv);
            continue;
        }
      

        // 没有找到相关指令
        printf("**  Unknown command: %s **\n", cli.curr_input);
    }
}

#if 0
    // open("tty:0",0);打开三次   可以但是没必要

    //  sbrk(0); // call 返回堆空间
    //  sbrk(100);
    //  sbrk(200);
    //  sbrk(4096 * 2 + 200);
    //  sbrk(4096 * 5 + 1234);
    //  \b 左移光标

    // printf("\n");
    // printf("\n");
    // printf("\nabef\b\b\b\bcd\n");                       // cdef
    // printf("\nabcd\x7f;fg\n");                          // abc;fg
    // printf("\n\0337Hello,word!\0338123\n");             // ESC 7,8 输出123lo,word!
    // printf("\n\033[31;42mHello,word!\033[39;49m123\n"); // ESC [pn m, Hello,world红色，>其余绿色
    // printf("\n123\033[2DHello,word!\n");                // 光标左移2，1Hello,word!
    // printf("\n123\033[2CHello,word!\n");                // 光标右移2，123  Hello,word!

    // printf("\n\033[31m");          // ESC [pn m, Hello,world红色，其余绿色
    //                                // \033[10;10H 光标定位到行列为 10 与 10 的位置
    // printf("\n\033[10;10H test!\n"); // 定位到10, 10，test!
    // printf("\n\033[20;20H test!\n"); // 定位到20, 20，test!
    // printf("\n\033[32;25;39m123\n"); // ESC [pn m, Hello,world红色，其余绿色
    // 对于使用了\n 外的转义字符的文字，\n将无法被扫描
    printf("===========================================================\n");
    fprintf(stderr, "error");
    printf("hello world!\n");
    printf("os version : %s\n", "1.0.0");
    printf("%d %d %d\n", 1, 2, 3);
    for (int i = 0; i < argc; i++)
    {
        printf("arg:%s\n", (uint32_t)argv[i]);
    }

    // fork();
    // yield();
#endif
